package com.lord.otter.component.utils.lock;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.lord.otter.component.context.spring.StaticSpringContext;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 基于Redisson的声明式事务锁工具类
 * 本工具类设计用于在Spring事务环境中使用，通过事务同步机制确保锁的自动释放
 * 所有锁操作必须在@Transactional注解的方法内调用，否则会抛出异常
 * <p>
 * 核心特性：
 * - 支持单个键和批量键的加锁操作
 * - 提供阻塞式lock和非阻塞式tryLock两种加锁方式
 * - 自动管理锁的生命周期，与事务绑定自动释放
 * - 内置死锁预防机制，通过全局锁确保批量操作的原子性
 * - 完善的异常处理和资源清理机制
 *
 * @author lord
 * 2025/11/7 15:57
 */
@Slf4j
public class TransactionalRedissonLockUtils {

    /**
     * Redisson客户端实例，通过静态初始化从Spring容器获取
     * 采用静态变量确保工具类的单例特性，避免重复创建客户端
     */
    private static final RedissonClient redissonClient;

    static {
        redissonClient = StaticSpringContext.getBean(RedissonClient.class);
    }

    /**
     * 全局锁键后缀，用于批量操作时防止死锁
     * 在批量加锁场景下，先获取全局锁确保操作的原子性，避免交叉加锁导致的死锁问题
     */
    private static final String GLOBAL_LOCK_SUFFIX = "_GLOBAL_BATCH_LOCK";

    /**
     * 默认锁等待时间，单位秒
     * 在分布式环境下，合理的等待时间可以平衡系统吞吐量和响应延迟
     */
    private static final long DEFAULT_WAIT_TIME = 30L;

    /**
     * 默认锁租期时间，单位秒
     * 租期时间应大于业务操作的最长执行时间，避免业务未完成锁自动过期
     */
    private static final long DEFAULT_LEASE_TIME = 60L;

    /**
     * 默认时间单位，统一使用秒作为时间单位
     * 保持时间单位的一致性有助于代码的可读性和维护性
     */
    private static final TimeUnit DEFAULT_TIME_UNIT = TimeUnit.SECONDS;

    // ==================== 阻塞式加锁方法 ====================

    /**
     * 对单个业务键进行阻塞式加锁操作
     * 使用默认的等待时间和租期时间配置，适用于大多数业务场景
     * 该方法会一直等待直到获取锁或超时，适合对资源强一致性要求高的场景
     *
     * @param key 锁的业务标识键，建议使用有业务意义的字符串如"order_update"
     * @throws LockOperationException       当获取锁超时或被中断时抛出
     * @throws NoActiveTransactionException 当不在事务上下文中调用时抛出
     */
    public static void lock(String key) {
        lock(key, DEFAULT_WAIT_TIME, DEFAULT_LEASE_TIME, DEFAULT_TIME_UNIT);
    }

    /**
     * 对单个业务键进行阻塞式加锁操作，支持自定义超时参数
     * 精确控制锁的等待时间和持有时间，适用于对性能有特殊要求的场景
     * 方法内部会验证参数有效性并检查事务上下文，确保操作的安全性
     *
     * @param key       锁的业务标识键，需要保证在分布式环境中的唯一性
     * @param waitTime  最大等待时间，0表示不等待立即返回，负数会被拒绝
     * @param leaseTime 锁的租期时间，必须大于0，表示锁自动释放的时间
     * @param unit      时间单位，用于解释waitTime和leaseTime参数
     * @throws IllegalArgumentException 当参数不合法时抛出
     * @throws LockOperationException   当加锁操作失败时抛出
     */
    public static void lock(String key, long waitTime, long leaseTime, TimeUnit unit) {
        // 前置条件检查：确保在事务环境中操作
        checkTransactionActive();

        // 参数有效性验证，防止无效参数导致不可预期的行为
        if (key == null || key.isEmpty()) {
            throw new IllegalArgumentException("锁键不能为空，请提供有效的业务标识");
        }
        validateTimeParams(waitTime, leaseTime, unit);

        // 获取Redisson锁实例，相同的key会返回同一个锁对象
        RLock lock = getRLock(key);
        try {
            // 尝试获取分布式锁，该方法会阻塞当前线程直到获取成功或超时
            boolean locked = lock.tryLock(waitTime, leaseTime, unit);
            if (!locked) {
                // 获取锁超时，抛出业务异常让调用方处理
                throw new LockOperationException("获取锁超时，可能由于锁被其他线程持有或系统繁忙，key: " + key);
            }

            // 注册事务完成后的回调，确保锁在事务提交或回滚后自动释放
            registerUnlockCallback(lock);

        } catch (InterruptedException e) {
            // 正确处理线程中断，恢复中断状态并抛出业务异常
            Thread.currentThread().interrupt();
            throw new LockOperationException("获取锁过程被中断，可能是系统关闭或人工干预，key: " + key, e);
        } catch (Exception e) {
            // 捕获其他异常，包装成业务异常抛出
            throw new LockOperationException("获取锁发生未知错误，请检查Redisson连接状态，key: " + key, e);
        }
    }

    // ==================== 非阻塞式尝试加锁方法 ====================

    /**
     * 非阻塞方式尝试获取单个业务键的锁
     * 立即返回加锁结果，不会阻塞当前线程，适合高并发场景下的快速失败策略
     * 使用默认租期时间，获取成功后锁会在指定时间后自动过期
     *
     * @param key 锁的业务标识键
     * @return true表示成功获取锁，false表示锁已被其他线程持有
     * @throws LockOperationException 当加锁过程中发生异常时抛出
     */
    public static boolean tryLock(String key) {
        return tryLock(key, 0, DEFAULT_LEASE_TIME, DEFAULT_TIME_UNIT);
    }

    /**
     * 非阻塞方式尝试获取锁，支持自定义租期时间
     * 适用于需要精确控制锁持有时间的场景，如短时任务处理
     *
     * @param key       锁的业务标识键
     * @param leaseTime 锁的租期时间，必须大于0
     * @param unit      时间单位
     * @return 加锁是否成功的布尔结果
     */
    public static boolean tryLock(String key, long leaseTime, TimeUnit unit) {
        return tryLock(key, 0, leaseTime, unit);
    }

    /**
     * 完整的非阻塞加锁方法，支持所有时间参数配置
     * 提供最大的灵活性，允许调用方根据具体场景调整加锁策略
     *
     * @param key       业务标识键，用于区分不同的锁资源
     * @param waitTime  等待时间，0表示立即返回不等待
     * @param leaseTime 锁租期时间，确保业务在此时长内完成
     * @param unit      时间单位参数
     * @return 加锁成功返回true，失败返回false
     */
    public static boolean tryLock(String key, long waitTime, long leaseTime, TimeUnit unit) {
        // 确保在事务上下文中执行，保持与事务的生命周期同步
        checkTransactionActive();

        if (key == null || key.isEmpty()) {
            throw new IllegalArgumentException("锁键不能为空，请提供有效的业务标识");
        }
        validateTimeParams(waitTime, leaseTime, unit);

        RLock lock = getRLock(key);
        try {
            boolean locked = lock.tryLock(waitTime, leaseTime, unit);
            if (locked) {
                // 成功获取锁后注册释放回调，与事务绑定
                registerUnlockCallback(lock);
                log.debug("成功获取分布式锁，key: {}", key);
            } else {
                log.debug("获取分布式锁失败，锁已被其他线程持有，key: {}", key);
            }
            return locked;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new LockOperationException("尝试获取锁时线程被中断，key: " + key, e);
        } catch (Exception e) {
            throw new LockOperationException("尝试获取锁时发生系统异常，key: " + key, e);
        }
    }

    /**
     * 非阻塞方式尝试获取单个数据键的锁
     * 适用于对单个数据记录进行加锁的场景，如订单状态更新
     *
     * @param key     业务主键，用于标识业务领域
     * @param dataKey 数据键，具体的数据记录标识
     * @return 加锁是否成功
     */
    public static boolean tryLock(String key, String dataKey) {
        return tryLock(key, Sets.newHashSet(dataKey), 0, DEFAULT_LEASE_TIME, DEFAULT_TIME_UNIT);
    }

    /**
     * 批量数据键的非阻塞加锁尝试
     * 尝试获取多个数据记录的锁，任一失败则立即返回，不会部分持有锁
     * 这种方式可以避免死锁和资源浪费，适合需要原子性操作的场景
     *
     * @param key      业务主键，如"order_processing"
     * @param dataKeys 数据键集合，需要加锁的具体数据标识
     * @return 成功获取所有锁返回true，任一失败返回false
     */
    public static boolean tryLock(String key, Set<String> dataKeys) {
        return tryLock(key, dataKeys, 0, DEFAULT_LEASE_TIME, DEFAULT_TIME_UNIT);
    }

    /**
     * 完整的批量非阻塞加锁方法
     * 采用两阶段加锁策略：先获取全局锁，再按顺序获取数据锁，最后释放全局锁
     * 这种设计可以有效预防死锁，确保批量操作的原子性和一致性
     *
     * @param key       业务主键，用于命名空间隔离
     * @param dataKeys  需要加锁的数据键集合
     * @param waitTime  等待时间配置
     * @param leaseTime 锁租期时间
     * @param unit      时间单位
     * @return 是否成功获取所有需要的锁
     */
    public static boolean tryLock(String key, Set<String> dataKeys, long waitTime, long leaseTime, TimeUnit unit) {
        checkTransactionActive();

        if (key == null || key.isEmpty()) {
            throw new IllegalArgumentException("业务主键不能为空，用于标识锁的业务领域");
        }
        if (dataKeys == null || dataKeys.isEmpty()) {
            throw new IllegalArgumentException("数据键集合不能为空，请指定需要加锁的具体数据");
        }
        validateTimeParams(waitTime, leaseTime, unit);

        // 构造全局锁键，用于防止批量操作时的死锁问题
        String globalLockKey = key + GLOBAL_LOCK_SUFFIX;
        RLock globalLock = null;
        List<RLock> acquiredLocks = new ArrayList<>();

        try {
            // 第一阶段：获取全局锁，使用较短的等待时间避免长时间阻塞
            long globalWaitTime = Math.min(waitTime, 5L);
            globalLock = getRLock(globalLockKey);
            boolean globalLocked = globalLock.tryLock(globalWaitTime, leaseTime, unit);

            if (!globalLocked) {
                log.debug("获取全局锁失败，可能其他线程正在执行批量操作，globalKey: {}", globalLockKey);
                return false;
            }

            // 第二阶段：按顺序尝试获取所有数据锁，确保不会产生死锁
            acquiredLocks = tryAcquireDataLocks(key, dataKeys, waitTime, leaseTime, unit);
            boolean allLocksAcquired = acquiredLocks.size() == dataKeys.size();

            if (allLocksAcquired) {
                log.debug("批量加锁操作成功，共获取{}把数据锁，业务域: {}", dataKeys.size(), key);
                // 注册事务回调，确保锁在事务结束时自动释放
                registerUnlockCallback(acquiredLocks);
            } else {
                log.debug("批量加锁操作未完成，成功获取{}/{}把锁，业务域: {}",
                        acquiredLocks.size(), dataKeys.size(), key);
            }

            // 第三阶段：释放全局锁，无论数据锁获取是否成功都要释放全局锁
            if (globalLock.isHeldByCurrentThread()) {
                globalLock.unlock();
            }

            return allLocksAcquired;

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            releaseAllLocks(acquiredLocks);
            releaseGlobalLock(globalLockKey);
            throw new LockOperationException("批量获取锁过程被意外中断，业务域: " + key, e);
        } catch (Exception e) {
            releaseAllLocks(acquiredLocks);
            releaseGlobalLock(globalLockKey);
            throw new LockOperationException("批量获取锁发生系统异常，业务域: " + key, e);
        }
    }

    // ==================== 批量加锁方法（原有）====================

    /**
     * 对单个数据键进行批量加锁操作
     * 实际上是批量操作的特例，保持API的一致性
     */
    public static void lock(String key, String dataKey) {
        lock(key, Sets.newHashSet(dataKey), DEFAULT_WAIT_TIME, DEFAULT_LEASE_TIME, DEFAULT_TIME_UNIT);
    }

    /**
     * 批量数据键的阻塞式加锁操作
     * 采用全局锁+数据锁的两阶段加锁模式，确保操作的原子性
     * 适合需要同时锁定多个资源的复杂业务场景
     */
    public static void lock(String key, Set<String> dataKeys, long waitTime, long leaseTime, TimeUnit unit) {
        checkTransactionActive();

        if (key == null || key.isEmpty()) {
            throw new IllegalArgumentException("业务主键不能为空");
        }
        if (dataKeys == null || dataKeys.isEmpty()) {
            throw new IllegalArgumentException("数据键集合不能为空");
        }
        validateTimeParams(waitTime, leaseTime, unit);

        String globalLockKey = key + GLOBAL_LOCK_SUFFIX;

        try {
            // 先获取全局锁，防止并发批量操作产生死锁
            long globalWaitTime = Math.min(waitTime, 5L);
            lock(globalLockKey, globalWaitTime, leaseTime, unit);

            // 按顺序获取所有数据锁，确保锁的获取顺序一致性
            List<RLock> dataLocks = acquireDataLocks(key, dataKeys, waitTime, leaseTime, unit);

            // 立即释放全局锁，减少锁的持有时间提高并发性
            RLock globalLock = getRLock(globalLockKey);
            if (globalLock.isHeldByCurrentThread()) {
                globalLock.unlock();
            }

            // 注册事务回调，数据锁会在事务完成后自动释放
            registerUnlockCallback(dataLocks);

        } catch (Exception e) {
            // 异常时确保全局锁被释放，避免资源泄漏
            releaseGlobalLock(globalLockKey);
            throw new LockOperationException("批量加锁操作失败，业务域: " + key, e);
        }
    }

    // ==================== 私有工具方法 ====================

    /**
     * 尝试按顺序获取数据锁集合（非阻塞方式）
     * 对数据键进行排序后按顺序尝试加锁，这是预防死锁的经典方法
     * 任一锁获取失败立即停止，确保不会产生部分加锁的状态
     */
    private static List<RLock> tryAcquireDataLocks(String key, Set<String> dataKeys,
                                                   long waitTime, long leaseTime, TimeUnit unit)
            throws InterruptedException {
        // 对数据键进行排序，确保所有线程以相同顺序加锁，避免死锁
        List<String> sortedKeys = dataKeys.stream()
                .map(dataKey -> key + ":" + dataKey)
                .sorted()
                .collect(Collectors.toList());

        List<RLock> locks = sortedKeys.stream()
                .map(TransactionalRedissonLockUtils::getRLock)
                .collect(Collectors.toList());

        List<RLock> acquiredLocks = new ArrayList<>();
        long startTime = System.currentTimeMillis();
        long remainingTime = unit.toMillis(waitTime);

        for (RLock lock : locks) {
            long lockStartTime = System.currentTimeMillis();
            long currentRemainingTime = remainingTime - (System.currentTimeMillis() - startTime);

            // 检查剩余时间，避免无谓的等待
            if (currentRemainingTime <= 0) {
                log.debug("获取数据锁超时，剩余时间不足，锁名: {}", lock.getName());
                break;
            }

            // 尝试获取当前数据锁
            boolean locked = lock.tryLock(
                    Math.max(1, TimeUnit.MILLISECONDS.toSeconds(currentRemainingTime)),
                    leaseTime, unit
            );

            if (locked) {
                acquiredLocks.add(lock);
                log.debug("成功获取数据锁: {}, 耗时: {}ms", lock.getName(),
                        System.currentTimeMillis() - lockStartTime);
            } else {
                log.debug("获取数据锁失败: {}, 可能被其他线程持有", lock.getName());
                // 非阻塞方式下，任一失败就停止继续获取
                break;
            }
        }

        return acquiredLocks;
    }

    /**
     * 安全释放全局锁的辅助方法
     * 在异常处理或正常流程中确保全局锁被正确释放
     * 采用try-catch包装，避免释放操作抛出异常影响主流程
     */
    private static void releaseGlobalLock(String globalLockKey) {
        try {
            RLock globalLock = getRLock(globalLockKey);
            if (globalLock.isHeldByCurrentThread()) {
                globalLock.unlock();
                log.debug("安全释放全局锁: {}", globalLockKey);
            }
        } catch (Exception e) {
            log.error("释放全局锁时发生异常，锁名: {}", globalLockKey, e);
        }
    }

    /**
     * 阻塞式获取数据锁集合的实现
     * 与tryAcquireDataLocks不同，该方法会等待直到获取所有锁或超时
     * 任一锁获取失败会释放已获取的所有锁，确保资源清理
     */
    private static List<RLock> acquireDataLocks(String key, Set<String> dataKeys,
                                                long waitTime, long leaseTime, TimeUnit unit) {
        List<String> sortedKeys = dataKeys.stream()
                .map(dataKey -> key + ":" + dataKey)
                .sorted()
                .collect(Collectors.toList());

        List<RLock> locks = sortedKeys.stream()
                .map(TransactionalRedissonLockUtils::getRLock)
                .collect(Collectors.toList());

        List<RLock> acquiredLocks = new ArrayList<>();
        long startTime = System.currentTimeMillis();
        long remainingTime = unit.toMillis(waitTime);

        for (RLock lock : locks) {
            try {
                long lockStartTime = System.currentTimeMillis();

                long currentRemainingTime = remainingTime - (System.currentTimeMillis() - startTime);
                if (currentRemainingTime <= 0) {
                    throw new LockOperationException("获取数据锁超时，请检查系统负载或调整超时时间，锁名: " + lock.getName());
                }

                boolean locked = lock.tryLock(
                        Math.max(1, TimeUnit.MILLISECONDS.toSeconds(currentRemainingTime)),
                        leaseTime, unit
                );

                if (!locked) {
                    throw new LockOperationException("获取数据锁失败，可能由于网络问题或锁竞争激烈，锁名: " + lock.getName());
                }

                acquiredLocks.add(lock);
                log.debug("阻塞式获取锁成功: {}, 耗时: {}ms", lock.getName(),
                        System.currentTimeMillis() - lockStartTime);

            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                releaseAllLocks(acquiredLocks);
                throw new LockOperationException("获取数据锁时线程被中断，锁名: " + lock.getName(), e);
            } catch (Exception e) {
                releaseAllLocks(acquiredLocks);
                throw new LockOperationException("获取数据锁时发生系统异常，锁名: " + lock.getName(), e);
            }
        }

        return acquiredLocks;
    }

    /**
     * 事务上下文检查方法
     * 确保所有锁操作都在Spring事务管理下执行，避免锁泄漏问题
     * 这是工具类安全性的重要保障措施
     */
    private static void checkTransactionActive() {
        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
            throw new NoActiveTransactionException("当前无活跃事务上下文！请确保在@Transactional注解的方法内调用锁操作");
        }
    }

    /**
     * 时间参数验证工具方法
     * 对输入的时间参数进行合理性检查，提前发现配置错误
     */
    private static void validateTimeParams(long waitTime, long leaseTime, TimeUnit unit) {
        if (waitTime < 0) {
            throw new IllegalArgumentException("等待时间不能为负数，请提供有效的超时配置");
        }
        if (leaseTime <= 0) {
            throw new IllegalArgumentException("租期时间必须大于0，确保锁有合理的生命周期");
        }
        if (unit == null) {
            throw new IllegalArgumentException("时间单位不能为空，请指定明确的时间单位");
        }
    }

    /**
     * 获取Redisson锁实例的工厂方法
     * 封装Redisson客户端的锁获取逻辑，便于统一管理和扩展
     */
    private static RLock getRLock(String key) {
        return redissonClient.getLock(key);
    }

    /**
     * 安全释放锁集合的通用方法
     * 遍历锁集合，对当前线程持有的锁进行释放操作
     * 采用异常捕获机制，确保单个锁释放失败不影响其他锁的释放
     */
    private static void releaseAllLocks(List<RLock> locks) {
        if (locks == null || locks.isEmpty()) return;

        for (RLock lock : locks) {
            try {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                    log.debug("安全释放锁资源: {}", lock.getName());
                }
            } catch (Exception e) {
                log.error("释放锁资源时发生异常，锁名: {}", lock.getName(), e);
            }
        }
    }

    /**
     * 注册单个锁的事务释放回调
     * 将锁的生命周期与Spring事务绑定，实现自动化的资源管理
     */
    private static void registerUnlockCallback(RLock lock) {
        registerUnlockCallback(Lists.newArrayList(lock));
    }

    /**
     * 注册锁集合的事务释放回调
     * 利用Spring的事务同步机制，在事务完成时自动触发锁释放
     * 无论是事务提交还是回滚，都会确保锁被正确释放
     */
    private static void registerUnlockCallback(List<RLock> locks) {
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCompletion(int status) {
                log.debug("事务执行完成，状态码: {}，开始自动释放{}把分布式锁", status, locks.size());
                releaseAllLocks(locks);
            }
        });
    }

    // ==================== 异常定义类 ====================

    /**
     * 锁操作业务异常类
     * 用于封装加锁、释放锁过程中发生的业务逻辑异常
     * 与系统异常区分，便于调用方进行针对性的异常处理
     */
    public static class LockOperationException extends RuntimeException {
        public LockOperationException(String message) {
            super(message);
        }

        public LockOperationException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    /**
     * 事务上下文异常类
     * 当检测到不在事务环境中使用锁工具时抛出
     * 提醒开发者正确使用工具类，避免资源管理问题
     */
    public static class NoActiveTransactionException extends RuntimeException {
        public NoActiveTransactionException(String message) {
            super(message);
        }
    }
}